package core.net;


import java.io.File;
import java.io.IOException;
import java.util.GregorianCalendar;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import core.Model;
import core.utils.LogData;
import core.utils.Variable;
import core.utils.VariableDataSet;

/**
 * Class for parsing XML-files and translating the XML-elements into <code>LogData</code>-objects.
 * All methods throw IllegalStateExceptions unless a prior call has been made to the
 * <code>initialize</code>-method.
 */
public class XmlParser {
    private static boolean initialized = false;
    private static DocumentBuilder docBuilder;
    private static Document cachedDoc = null;
    private static int dataPointsRead = 0;

    private static void initialize() {
        if (!initialized){
            try {
                docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                initialized = true;
            } catch (ParserConfigurationException e){
                e.printStackTrace();
            }
        }
    }

    /**
     * Creates a sorted set of datasets from XML document on webserver.
     * @return A sorted set of datasets from XML document on webserver.
     * @throws IOException If a connection error is encountered.
     */
    public static TreeSet<VariableDataSet> readDocument() throws IOException{
        try {
            readXml();
        } catch (IllegalStateException e) {
            System.err.println("Parser not initialized");
        }

        return parseXml();
    }
    
	public static TreeSet<VariableDataSet> parseLog(File file){
		readLog(file);
		
		return parseXml();
	}
	
	private static void readLog(File file){
		if (!initialized){
			initialize();
		}
		try {
			cachedDoc = docBuilder.parse(file);
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
			System.err.println("IOException in readLog. Why?");
		}
	}

    private static TreeSet<VariableDataSet> parseXml(){
        TreeSet<VariableDataSet> data = new TreeSet<VariableDataSet>();

        Element root = cachedDoc.getDocumentElement();

        NodeList signals = root.getElementsByTagName("signal");
       
        Element e;

        TreeSet<LogData> signalData;
        LogData dataPoint;
        int id;
        for (int i = 0; i < Model.getVariables().size(); i++){
            signalData = new TreeSet<LogData>();
            e = (Element) signals.item(i);
            System.out.println("Signal id: " + (i+1));
            id = Integer.parseInt(e.getAttribute("id"));

            NodeList dataPoints = e.getElementsByTagName("signalData");
            Element dataPointElement;

            dataPointElement = (Element) dataPoints.item(i);
            double signalValue = Double.parseDouble(getStringByName(dataPointElement,"signalValue"));
            double xCoord = Double.parseDouble(getStringByName(dataPointElement,"xCoordinate"));
            double yCoord = Double.parseDouble(getStringByName(dataPointElement,"yCoordinate"));
            boolean warning = (getStringByName(dataPointElement, "warning").equals("1"));
            long baseLineTime = Long.parseLong(getStringByName(dataPointElement,"time"));
            signalData.add(new LogData(id, signalValue, 0, xCoord, yCoord, warning));
            
            long time;
            for (int j = 1; j < dataPoints.getLength(); j++){
                dataPointElement = (Element) dataPoints.item(j);
                signalValue = Double.parseDouble(getStringByName(dataPointElement,"signalValue"));
                xCoord = Double.parseDouble(getStringByName(dataPointElement,"xCoordinate"));
                yCoord = Double.parseDouble(getStringByName(dataPointElement,"yCoordinate"));
                warning = (getStringByName(dataPointElement, "warning").equals("1"));
                time = ((Long.parseLong(getStringByName(dataPointElement,"time")) - baseLineTime) / 1000L);
                dataPoint = new LogData(id, signalValue, time - baseLineTime, xCoord, yCoord, warning);
                signalData.add(dataPoint);
            }
            dataPointsRead += signalData.size();
            data.add(new VariableDataSet(getVariableFromId(id), signalData));
        }
        return data;
    }

    private static Variable getVariableFromId(int id){
        //-1 due to signals not being zero-indexed in the database.
        return Model.getVariables().get(id-1);
    }

    /**
     * Retrieves the String-content of the subnode to the provided <code>element</code> with name <code>tagName</code>.
     * Throws IllegalArgumentException if no subnode is found with name <code>tagName</code>.
     * @param element The parent node.
     * @param tagName The name of the sought after child-node.
     * @return The string content of the subnode if it exists.
     */
    private static String getStringByName(Element element, String tagName) throws IllegalArgumentException{
        if (element == null){
            throw new IllegalArgumentException("element is null");
        }
        Node n = element.getElementsByTagName(tagName).item(0);
        if (n != null){
            return n.getTextContent();
        }
        else {
            throw new IllegalArgumentException("No subnode found with name equal to \"" + tagName + "\"");
        }
    }

    /**
     * Forces a new log to be fetched from web-sever
     */
    private static void readXml() throws IOException{
        if (!initialized){
            initialize();
        }

        try {
            cachedDoc = docBuilder.parse(ConnectionHandler.getInstance().getDataStream(dataPointsRead));
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }
}
